package cl.utfsm.ci.persistence.services.impl;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityExistsException;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaQuery;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cl.utfsm.ci.persistence.services.PersistenceService;
import cl.utfsm.ci.persistence.services.impl.AbstractGenericDAO;
/**
 *  Provee los servicios de persistencia basicos para los objetos de acceso a datos.
 * 
 */

public abstract class AbstractGenericDAO implements PersistenceService{
	
	protected Logger logger = LoggerFactory.getLogger(AbstractGenericDAO.class);
	
	
	private EntityManager entityManager;
	private boolean innerTransactionOn=true;
	private EntityTransaction tx = null;

	public EntityManager getEntityManager() {
		return entityManager;
	}

	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}
	
	private void beginInnerTransaction() {
		if (this.innerTransactionOn){
			try{
				tx = getEntityManager().getTransaction(); 
				tx.begin();			
				logger.info("Iniciando inner transaccion");				
				logger.debug("Iniciando inner transaccion");				
			}catch(Exception exception){
				throw new PersistenceException("Imposible iniciar la transaccion",exception);
			}

		}

	}
	private void commitInnerTransaction() {
		if (this.innerTransactionOn){
			try{				
				tx.commit();				
				logger.info("Cerrando inner transaccion");				
				logger.debug("Cerrando inner transaccion");				
			}catch(Exception exception){
				throw new PersistenceException("Imposible completar la transaccion",exception);
			}
			
		}
		
	}
	
	private void setInnerTransactionOn() { this.innerTransactionOn =true; }
	private void setInnerTransactionOff() { this.innerTransactionOn =false; }
	
	public void beginTransaction() {
		setInnerTransactionOff();	
		tx =getEntityManager().getTransaction(); 
		tx.begin();		
		logger.debug("Iniciando transaccion");		
	}

	private void checkPersistenceException(PersistenceException exception) throws PersistenceException, EntityExistsException {
		Exception cause = (Exception) exception.getCause();
		if ( cause!=null) {
			cause = (Exception) cause.getCause();
			if (cause!=null){
				cause = (Exception) cause.getCause();
				/*if (cause instanceof PSQLException){
					String message = cause.getMessage();				
					int idx = message.indexOf("ERROR: duplicate key value violates unique constraint");
					if (idx>=0)
						throw new EntityExistsException(exception);
				}*/				
			}
		}
		throw exception;
	}
	
	public void commitTransaction() {
		try{
			tx.commit();
			logger.debug("Cerrando transaccion");			
		}catch(javax.persistence.RollbackException exception){
			checkPersistenceException(new PersistenceException("Imposible completar la transaccion",exception));
			//throw new PersistenceException("Imposible completar la transaccion",exception);
		}finally{
			setInnerTransactionOn();			
		}
		
	}
	public void rollbackTransaction(){
		try{
			tx.rollback();
			setInnerTransactionOn();
			logger.debug("Rollback de la transaccion");			
		}catch(Exception exception){
			throw new PersistenceException("Imposible hacer rollback de la transaccion",exception);
		}finally{
			setInnerTransactionOn();			
		}
		
	}
	public <T> T crear(T entity)  {
		beginInnerTransaction();
		getEntityManager().persist(entity);
		commitInnerTransaction();
		return entity;
	}

	public <T> void borrar(T entity) {
			beginInnerTransaction();		
			T entityToBeRemoved = getEntityManager().merge(entity);
			getEntityManager().remove(entityToBeRemoved);
			commitInnerTransaction();
	}	

	public <T> T actualizar(T entity)  {
		beginInnerTransaction();		
		entity = getEntityManager().merge(entity);
		commitInnerTransaction();		
		return entity;
	}	
	
	@SuppressWarnings("unchecked")
	public <T> T buscar(String entityQuery, Map<String, Object> parametros) {
		T result = null;
		try {			
			
			Query query = getEntityManager().createQuery(entityQuery);
			if (parametros != null && !parametros.isEmpty()) {
				llenarParametrosQuery(query, parametros);
			}
			result = (T) query.getSingleResult(); 
		} catch (NoResultException e) {
			logger.warn("Ningun resultado encontrado para consulta con: " + entityQuery, e);
		} catch (Exception e) {
			logger.warn("Error al ejecutar consulta: " + e.getMessage(), e);
			throw new PersistenceException(e);
		}
		return result;
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public  <T> List<T> lista(Class<T> entityClass) throws  PersistenceException {

		try {			
			CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
			cq.select(cq.from(entityClass));
			return getEntityManager().createQuery(cq).getResultList();
		} catch (Exception e) {
			logger.warn("Error al ejecutar consulta: " + e.getMessage(), e);
			throw new PersistenceException(e);
		} 
	}
	
	private void llenarParametrosQuery(Query query, Map<String, Object> parameters) {
		for (Entry<String, Object> entry : parameters.entrySet()) {
			query.setParameter(entry.getKey(), entry.getValue());
		}
	}	
}

